From b56b61c56e709272863ffb1c86eda77479760d88 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 20 Feb 2016 10:36:09 -0800 Subject: [PATCH] Implement a fallible PackageSet::get This function will lazily download the package specified and fill a cell with that package. Currently this is always infallible because the `PackageSet` is initialized with all packages, but eventually this will not be true. --- src/cargo/core/package.rs | 43 +++++++++++------- src/cargo/ops/cargo_clean.rs | 2 +- src/cargo/ops/cargo_compile.rs | 4 +- src/cargo/ops/cargo_output_metadata.rs | 7 ++- src/cargo/ops/cargo_rustc/context.rs | 3 +- src/cargo/ops/cargo_rustc/fingerprint.rs | 3 +- src/cargo/ops/cargo_rustc/job_queue.rs | 35 ++++++++------- src/cargo/ops/cargo_rustc/mod.rs | 2 +- src/cargo/util/dependency_queue.rs | 20 ++++----- src/cargo/util/lazy_cell.rs | 55 ++++++++++++++++++++++++ src/cargo/util/mod.rs | 3 +- 11 files changed, 123 insertions(+), 54 deletions(-) create mode 100644 src/cargo/util/lazy_cell.rs diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index e671a15a6..b0c0c2461 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -1,15 +1,15 @@ +use std::cell::{Ref, RefCell}; use std::collections::HashMap; use std::fmt; use std::hash; -use std::iter; use std::path::{Path, PathBuf}; -use std::slice; + use semver::Version; use core::{Dependency, Manifest, PackageId, SourceId, Target}; use core::{Summary, Metadata, SourceMap}; use ops; -use util::{CargoResult, Config}; +use util::{CargoResult, Config, LazyCell, ChainError, internal}; use rustc_serialize::{Encoder,Encodable}; /// Information about a package that is available somewhere in the file system. @@ -120,30 +120,43 @@ impl hash::Hash for Package { } pub struct PackageSet<'cfg> { - packages: Vec, - sources: SourceMap<'cfg>, + packages: Vec<(PackageId, LazyCell)>, + sources: RefCell>, } impl<'cfg> PackageSet<'cfg> { pub fn new(packages: Vec, sources: SourceMap<'cfg>) -> PackageSet<'cfg> { PackageSet { - packages: packages, - sources: sources, + packages: packages.into_iter().map(|pkg| { + (pkg.package_id().clone(), LazyCell::new(Some(pkg))) + }).collect(), + sources: RefCell::new(sources), } } - pub fn package_ids(&self) -> iter::Map, - fn(&Package) -> &PackageId> { - let f = Package::package_id as fn(&Package) -> &PackageId; - self.packages.iter().map(f) + pub fn package_ids<'a>(&'a self) -> Box + 'a> { + Box::new(self.packages.iter().map(|&(ref p, _)| p)) } - pub fn get(&self, id: &PackageId) -> &Package { - self.packages.iter().find(|pkg| pkg.package_id() == id).unwrap() + pub fn get(&self, id: &PackageId) -> CargoResult<&Package> { + let slot = try!(self.packages.iter().find(|p| p.0 == *id).chain_error(|| { + internal(format!("couldn't find `{}` in package set", id)) + })); + let slot = &slot.1; + if let Some(pkg) = slot.borrow() { + return Ok(pkg) + } + let mut sources = self.sources.borrow_mut(); + let source = try!(sources.get_mut(id.source_id()).chain_error(|| { + internal(format!("couldn't find source for `{}`", id)) + })); + let pkg = try!(source.download(id)); + assert!(slot.fill(pkg).is_ok()); + Ok(slot.borrow().unwrap()) } - pub fn sources(&self) -> &SourceMap<'cfg> { - &self.sources + pub fn sources(&self) -> Ref> { + self.sources.borrow() } } diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 135943827..c4918f9c3 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -42,7 +42,7 @@ pub fn clean(manifest_path: &Path, opts: &CleanOptions) -> CargoResult<()> { for spec in opts.spec { // Translate the spec to a Package let pkgid = try!(resolve.query(spec)); - let pkg = packages.get(&pkgid); + let pkg = try!(packages.get(&pkgid)); // And finally, clean everything out! for target in pkg.targets() { diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 3286db42f..7d0f858ea 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -180,7 +180,9 @@ pub fn compile_pkg<'a>(root_package: &Package, invalid_spec.join(", ")) } - let to_builds = pkgids.iter().map(|id| packages.get(id)).collect::>(); + let to_builds = try!(pkgids.iter().map(|id| { + packages.get(id) + }).collect::>>()); let mut general_targets = Vec::new(); let mut package_targets = Vec::new(); diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index 7902ea3e9..7a037828d 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -51,10 +51,9 @@ fn metadata_full(opt: OutputMetadataOptions, config: &Config) -> CargoResult Context<'a, 'cfg> { /// Gets a package for the given package id. pub fn get_package(&self, id: &PackageId) -> &'a Package { - self.packages.get(id) + // TODO: remove this unwrap() -- happens in a later commit + self.packages.get(id).unwrap() } /// Get the user-specified linker for a particular host or target diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 09d3300cc..386ed7792 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -562,7 +562,8 @@ fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { fn pkg_fingerprint(cx: &Context, pkg: &Package) -> CargoResult { let source_id = pkg.package_id().source_id(); - let source = try!(cx.packages.sources().get(source_id).chain_error(|| { + let sources = cx.packages.sources(); + let source = try!(sources.get(source_id).chain_error(|| { internal("missing package source") })); source.fingerprint(pkg) diff --git a/src/cargo/ops/cargo_rustc/job_queue.rs b/src/cargo/ops/cargo_rustc/job_queue.rs index 30d6394c2..6ef8f7d35 100644 --- a/src/cargo/ops/cargo_rustc/job_queue.rs +++ b/src/cargo/ops/cargo_rustc/job_queue.rs @@ -8,7 +8,7 @@ use term::color::YELLOW; use core::{PackageId, Target, Profile}; use util::{Config, DependencyQueue, Fresh, Dirty, Freshness}; -use util::{CargoResult, Dependency, profile, internal}; +use util::{CargoResult, profile, internal}; use super::{Context, Kind, Unit}; use super::job::Job; @@ -68,10 +68,13 @@ impl<'a> JobQueue<'a> { } } - pub fn enqueue(&mut self, cx: &Context<'a, 'a>, - unit: &Unit<'a>, job: Job, fresh: Freshness) { + pub fn enqueue<'cfg>(&mut self, cx: &Context<'a, 'cfg>, + unit: &Unit<'a>, job: Job, fresh: Freshness) { let key = Key::new(unit); - self.queue.queue(cx, Fresh, key, Vec::new()).push((job, fresh)); + self.queue.queue(Fresh, + key, + Vec::new(), + &key.dependencies(cx)).push((job, fresh)); *self.counts.entry(key.pkg).or_insert(0) += 1; } @@ -230,10 +233,17 @@ impl<'a> JobQueue<'a> { } } -impl<'a> Dependency for Key<'a> { - type Context = Context<'a, 'a>; +impl<'a> Key<'a> { + fn new(unit: &Unit<'a>) -> Key<'a> { + Key { + pkg: unit.pkg.package_id(), + target: unit.target, + profile: unit.profile, + kind: unit.kind, + } + } - fn dependencies(&self, cx: &Context<'a, 'a>) -> Vec> { + fn dependencies<'cfg>(&self, cx: &Context<'a, 'cfg>) -> Vec> { let unit = Unit { pkg: cx.get_package(self.pkg), target: self.target, @@ -252,17 +262,6 @@ impl<'a> Dependency for Key<'a> { } } -impl<'a> Key<'a> { - fn new(unit: &Unit<'a>) -> Key<'a> { - Key { - pkg: unit.pkg.package_id(), - target: unit.target, - profile: unit.profile, - kind: unit.kind, - } - } -} - impl<'a> fmt::Debug for Key<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} => {}/{} => {:?}", self.pkg, self.target, self.profile, diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 7f2deb917..0306c29b3 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -79,7 +79,7 @@ pub fn compile_targets<'a, 'cfg: 'a>(pkg_targets: &'a PackagesToBuild<'a>, }).collect::>(); let dest = if build_config.release {"release"} else {"debug"}; - let root = packages.get(resolve.root()); + let root = try!(packages.get(resolve.root())); let host_layout = Layout::new(config, root, None, &dest); let target_layout = build_config.requested_target.as_ref().map(|target| { layout::Layout::new(config, root, Some(&target), &dest) diff --git a/src/cargo/util/dependency_queue.rs b/src/cargo/util/dependency_queue.rs index 9db5d103c..5165adc24 100644 --- a/src/cargo/util/dependency_queue.rs +++ b/src/cargo/util/dependency_queue.rs @@ -46,19 +46,13 @@ pub enum Freshness { Dirty, } -/// A trait for discovering the dependencies of a piece of data. -pub trait Dependency: Hash + Eq + Clone { - type Context; - fn dependencies(&self, cx: &Self::Context) -> Vec; -} - impl Freshness { pub fn combine(&self, other: Freshness) -> Freshness { match *self { Fresh => other, Dirty => Dirty } } } -impl DependencyQueue { +impl DependencyQueue { /// Creates a new dependency queue with 0 packages. pub fn new() -> DependencyQueue { DependencyQueue { @@ -73,8 +67,11 @@ impl DependencyQueue { /// /// It is assumed that any dependencies of this package will eventually also /// be added to the dependency queue. - pub fn queue(&mut self, cx: &K::Context, fresh: Freshness, - key: K, value: V) -> &mut V { + pub fn queue(&mut self, + fresh: Freshness, + key: K, + value: V, + dependencies: &[K]) -> &mut V { let slot = match self.dep_map.entry(key.clone()) { Occupied(v) => return &mut v.into_mut().1, Vacant(v) => v, @@ -85,9 +82,10 @@ impl DependencyQueue { } let mut my_dependencies = HashSet::new(); - for dep in key.dependencies(cx).into_iter() { + for dep in dependencies { assert!(my_dependencies.insert(dep.clone())); - let rev = self.reverse_dep_map.entry(dep).or_insert(HashSet::new()); + let rev = self.reverse_dep_map.entry(dep.clone()) + .or_insert(HashSet::new()); assert!(rev.insert(key.clone())); } &mut slot.insert((my_dependencies, value)).1 diff --git a/src/cargo/util/lazy_cell.rs b/src/cargo/util/lazy_cell.rs new file mode 100644 index 000000000..a13eaf31b --- /dev/null +++ b/src/cargo/util/lazy_cell.rs @@ -0,0 +1,55 @@ +//! A lazily fill Cell, but with frozen contents. +//! +//! With a `RefCell`, the inner contents cannot be borrowed for the lifetime of +//! the entire object, but only of the borrows returned. A `LazyCell` is a +//! variation on `RefCell` which allows borrows tied to the lifetime of the +//! outer object. +//! +//! The limitation of a `LazyCell` is that after initialized, it can never be +//! modified. + +use std::cell::UnsafeCell; + +pub struct LazyCell { + inner: UnsafeCell>, +} + +impl LazyCell { + /// Creates a new empty lazy cell. + pub fn new(init: Option) -> LazyCell { + LazyCell { inner: UnsafeCell::new(init) } + } + + /// Put a value into this cell. + /// + /// This function will fail if the cell has already been filled. + pub fn fill(&self, t: T) -> Result<(), T> { + unsafe { + let slot = self.inner.get(); + if (*slot).is_none() { + *slot = Some(t); + Ok(()) + } else { + Err(t) + } + } + } + + /// Borrows the contents of this lazy cell for the duration of the cell + /// itself. + /// + /// This function will return `Some` if the cell has been previously + /// initialized, and `None` if it has not yet been initialized. + pub fn borrow(&self) -> Option<&T> { + unsafe { + (*self.inner.get()).as_ref() + } + } + + /// Consumes this `LazyCell`, returning the underlying value. + pub fn into_inner(self) -> Option { + unsafe { + self.inner.into_inner() + } + } +} diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index e2f2cb971..da0f63a6a 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -1,6 +1,5 @@ pub use self::cfg::{Cfg, CfgExpr}; pub use self::config::Config; -pub use self::dependency_queue::Dependency; pub use self::dependency_queue::{DependencyQueue, Fresh, Dirty, Freshness}; pub use self::errors::{CargoResult, CargoError, ChainError, CliResult}; pub use self::errors::{CliError, ProcessError, CargoTestError}; @@ -8,6 +7,7 @@ pub use self::errors::{Human, caused_human}; pub use self::errors::{process_error, internal_error, internal, human}; pub use self::graph::Graph; pub use self::hex::{to_hex, short_hash, hash_u64}; +pub use self::lazy_cell::LazyCell; pub use self::lev_distance::{lev_distance}; pub use self::paths::{join_paths, path2bytes, bytes2path, dylib_path}; pub use self::paths::{normalize_path, dylib_path_envvar, without_prefix}; @@ -37,3 +37,4 @@ mod rustc; mod sha256; mod shell_escape; mod vcs; +mod lazy_cell; -- 2.30.2